<?php
$GLOBALS['DB']	= new Db();

class Db
{
	protected $driver;
	protected $table;
	
	protected $stuff	= array();
	protected $last	= '';
	protected $hkey	= null;

	protected static $pool	= array();
	
	protected $dump		= 0;
	protected $dumpFile	= null;

	
	/**
	 * get instance of db
	 *
	 * @return Db
	 */
	static function get($model=null)
	{
		static $stack	= array();
		static $cursor	= null;
		
		if($model && $model!=$cursor) {
			if($cursor) {
				$stack[$cursor]	= get_object_vars($GLOBALS['DB']);
			}
			
			if(isset($stack[$model])) {
				foreach($stack[$model] as $k=>&$v) {
					$GLOBALS['DB']->$k	= $v;
				}
			}
			else {
				$cfg	= app_cfg('db');
				$GLOBALS['DB']->driver	= self::loadDriver(isset($cfg[$model]) ? $cfg[$model] : $cfg['default']);
				$GLOBALS['DB']->table	= self::table($model);
				$GLOBALS['DB']->stuff	= array();
				$GLOBALS['DB']->last	= '';
			}
			
			$cursor	= $model;
		}
		
		return $GLOBALS['DB'];
	}

	/**
	 * Alias of Db::get
	 */
	static function instance($model)
	{
		return self::get($model);
	}
	
	static function table($model)
	{
		static $hash	= array();
		
		if(is_object($model))
		{
			$model	= substr(get_class($model), 0, -5);
		}
		if(!isset($hash[$model]))
		{
			$tables	= app_cfg('table');
			$hash[$model]	= Core::$convent['table_prefix'] . (isset($tables[$model]) ? $tables[$model] : strtolower($model));
		}
		
		return $hash[$model];
	}
	
	/**
	 * todo: mysql有一个问题，使用相同用户名密码做两次连接且选择不同数据库时，只会产生一个实际连接，
	 * 		 这是mysql_connect的机制(说有办法解决，但感觉不好)，尽量避免这种情况
	 */
	static function loadDriver($mydsn)
	{
		if(!isset(self::$pool[$mydsn]))
		{
			$cfg	= parse_url($mydsn);
			$model	= 'DbDriver'.ucfirst($cfg['scheme']);
			import('#/model/'.$model.'.php');
			self::$pool[$mydsn]	= call_user_func(
					array($model, 'instance'),
					$cfg['host'], $cfg['user'], fetch_a_char('pass',$cfg), substr($cfg['path'],1),
					!empty($cfg['query']) && false!==strpos($cfg['query'], 'persist')
				);
			if(!property_exists(self::$pool[$mydsn], 'trans')){
				import('#/model/DbTransaction.php');
				self::$pool[$mydsn]->trans = new Transaction();
			}
		}
		return self::$pool[$mydsn];
	}
	
	/**
	 * 关闭所有数据库链接（如果需要）
	 */
	static function close()
	{
		if(self::$pool)
		{
			foreach(self::$pool as $ins)
			{
				$ins->close();
			}
			self::$pool	= array();
		}
	}
	
	//---------------------SELECT-------------------------
	
	/**
	 *
	 * @return Db
	 */
	function select($fields='*')
	{
		$this->stuff['case']	= 'select';
		if(is_array($fields)) {
			$qu		= $this->driver->qu();
			$tmp	= array();
			foreach($fields as $f){
				$tmp[]	= "{$qu}{$f}{$qu}";
			}
			$this->stuff['fields']	= join(',', $tmp);
			unset($tmp);
		}
		else{
			$this->stuff['fields']	= $fields;
		}
		return $this;
	}
	function getOne($res=null)
	{
		return $this->driver->fetchOne(($res===null) ? $this->exec() : $res);
	}
	/**
	 * 取单条结果集的首个字段值
	 * @return string|bool
	 */
	function getCol()
	{
		$t	= $this->driver->fetchOne($this->exec());
		return $t ? array_pop($t) : false;
	}
	/**
	 * 取多条结果集的首个字段值
	 * @return array|bool
	 */
	function getCols()
	{
		if($this->hkey) {
			$_hk	= $this->hkey;
			$this->stuff['fields']	.= ", {$_hk}";
			if($all = $this->getAll()) {
				$tmp	= array();
				foreach($all as $a){
					$tmp[$a[$_hk]]	= array_shift($a);
				}
				return $tmp;
			}
		}
		else {
			if($all = $this->getAll()) {
				$tmp	= array();
				foreach($all as $a){
					$tmp[]	= array_shift($a);
				}
				return $tmp;
			}
		}
		return false;
	}
	function getAll($res=null)
	{
		if($this->hkey)
		{
			$list	= ($res===null)
						? $this->driver->fetchAll($this->exec())
						: $this->driver->fetchAll($res);
			if($list) {
				$hkey	= $this->hkey=='_pk' ? $this->pk() : $this->hkey;
				$tmp	= array();
				foreach($list as $r)
				{
					$tmp[$r[$hkey]]	= $r;
				}
				$this->hkey	= null;
				return $tmp;
			}
			$this->hkey	= null;
			return false;
		}
		else
		{
			return ($res===null)
					? $this->driver->fetchAll($this->exec())
					: $this->driver->fetchAll($res);
		}
	}

	/**
	 *
	 * @return Db
	 */
	public function limit($s, $l=null)
	{
		if(is_array($s) && count($s)==2){
			$l = $s[1];
			$s = $s[0];
		}
		if(!$s && !$l) {
			return $this;
		}
		if($l===null) {
			$this->stuff['limit']	= $this->driver->makeLimit(0, $s);
		}
		else {
			$this->stuff['limit']	= $this->driver->makeLimit($s, $l);
		}
		return $this;
	}
	/**
	 *
	 * @return Db
	 */
	public function group($group)
	{
		$this->stuff['group']	= $group;
		return $this;
	}
	/**
	 *
	 * @return Db
	 */
	public function order($order)
	{
		if(false!==strpos($order, '_pk')){
			$order = str_replace('_pk', $this->pk(), $order);
		}
		$this->stuff['order']	= $order;
		return $this;
	}
	
	/**
	 * 做为返回结果集hash的key的字段
	 *
	 * @return Db
	 */
	public function hkey($key)
	{
		$this->hkey	= $key;
		return $this;
	}

	/**
	 * Retrieve amount
	 * @return Db
	 */
	function count($field='*')
	{
		return $this->select('COUNT('.$field.')');
	}
	
	/**
	 * Db::get('Book')->select('t0.*,t1.name,t2.body')
	 * 				->join(array('User','Comment'), 'left')
	 * 				->on(array('t0.uid=t1.uid', 't0.bid=t2.bid'))
	 * 				->where('t0.type=1')
	 * 				->getAll();
	 *
	 * @return Db
	 */
	function join($models, $type='left')
	{
		if(MOD_DEBUG)
		{
			switch($type)
			{
			case 'left':
			case 'right':
			case 'inner':
				break;
			default:
				Show::error("Unknow join type: {$type}");
			}
		}
		$this->stuff['case']	= 'join';
		$this->stuff['join_table']	= is_array($models) ? $models : array($models);
		$this->stuff['join_type']	= $type;
		return $this;
	}
	/**
	 * Part of join
	 * @return Db
	 */
	function on($match)
	{
		if(MOD_DEBUG)
		{
			if(!empty($this->stuff['join_table']))
			{
				if(
					!( is_array($match) && (count($match)==count($this->stuff['join_table'])) )
					&& 1!=count($this->stuff['join_table'])
				){
					Show::error('No match table join.');
				}
			}
			/** 可能以后会有其它语句的支持 */
			else
			{
				Show::error('No match syntax with: on()');
			}
		}
		$this->stuff['join_match']	= is_array($match) ? $match : array($match);
		return $this;
	}
	
	//---------------------other commands-------------------------
	
	/**
	 * Add new record
	 * @return Db
	 */
	function insert($data)
	{
		if(!$data) return false;
		$this->stuff['case']	= 'insert';
		$this->stuff['fields']	= $data;
		return $this;
	}
	
	/**
	 * update record
	 * @return Db
	 */
	function update($data)
	{
		if(!$data) return false;
		$this->stuff['case']	= 'update';
		$this->stuff['fields']	= $data;
		return $this;
	}

	/**
	 * delete record
	 * @return Db
	 */
	function delete()
	{
		$this->stuff['case']	= 'delete';
		return $this;
	}
	
	/**
	 * Replace attr with new data
	 * 条件里必需有主键或唯一索引
	 * @return Db
	 */
	function replace($data)
	{
		if(!$data) return false;
		$this->stuff['case']	= 'replace';
		$this->stuff['fields']	= $data;
		return $this;
	}
	
	/**
	 * Give sql condition
	 * @return Db
	 */
	public function where()
	{
		$this->stuff['where']	= $this->toCondition(func_get_args(), true);
		return $this;
	}
	
	
	//---------------------QUERY-------------------------
	
	/**
	 * Do db query
	 * @return boolean
	 */
	function exec()
	{
		$sql	=    '';
		switch($this->stuff['case'])
		{
		case 'select':
			$sql	= "SELECT {$this->stuff['fields']} FROM {$this->table}";
			empty($this->stuff['where']) ? null : $sql.=(' WHERE '.$this->stuff['where']);
			empty($this->stuff['group']) ? null : $sql.=(' GROUP BY '.$this->stuff['group']);
			empty($this->stuff['order']) ? null : $sql.=(' ORDER BY '.$this->stuff['order']);
			empty($this->stuff['limit']) ? null : $sql.=(' '.$this->stuff['limit']);
			break;
			
		case 'insert':
			$sql	= 'INSERT INTO '.$this->table.' SET '. $this->buildRequest($this->stuff['fields']);
			break;
			
		case 'replace':
			$sql	= 'REPLACE '.$this->table.' SET '. $this->buildRequest($this->stuff['fields']);
			break;
			
		case 'update':
			$sql	= 'UPDATE '.$this->table.' SET '. $this->buildRequest($this->stuff['fields']);
			empty($this->stuff['where']) ? null : $sql.=(' WHERE '.$this->stuff['where']);
			empty($this->stuff['limit']) ? null : $sql.=(' '.$this->stuff['limit']);
			break;
			
		case 'delete':
			$sql	= 'DELETE FROM '.$this->table;
			empty($this->stuff['where']) ? null : $sql.=(' WHERE '.$this->stuff['where']);
			empty($this->stuff['order']) ? null : $sql.=(' ORDER BY '.$this->stuff['order']);
			empty($this->stuff['limit']) ? null : $sql.=(' '.$this->stuff['limit']);
			break;
			
		case 'join':
			$sql	= "SELECT {$this->stuff['fields']} FROM {$this->table} AS t0";
			
			$type	= strtoupper($this->stuff['join_type']);
			$jmatch	= $this->stuff['join_match'];
			$jary	= array();
			foreach($this->stuff['join_table'] as $k=>$t)
			{
				$jary[]	= sprintf('%s JOIN %s AS t%d ON %s', $type, self::table($t), $k+1, $jmatch[$k]);
			}
			$sql	.= ' '.join(' ', $jary);
			
			empty($this->stuff['where']) ? null : $sql.=(' WHERE '.$this->stuff['where']);
			empty($this->stuff['group']) ? null : $sql.=(' GROUP BY '.$this->stuff['group']);
			empty($this->stuff['order']) ? null : $sql.=(' ORDER BY '.$this->stuff['order']);
			empty($this->stuff['limit']) ? null : $sql.=(' '.$this->stuff['limit']);
			
			unset($type, $jmatch, $jary,
				$this->stuff['join_type'], $this->stuff['join_match'], $this->stuff['join_table']);
			break;
		}
		
		$this->stuff	= array();
		
		return $this->query($sql);
	}
	
	function query($sql)
	{
		if(false!==strpos($sql, '$0')) {
			$sql = str_replace('$0', $this->table, $sql);
		}
		
		if($this->dump)
		{
			if($this->dumpFile){
				Debug::file($sql, 0, $this->dumpFile);
			}
			else{
				Debug::pre($sql);
			}
			if(2===$this->dump) die;
			
			$this->dump		= 0;
			$this->dumpFile	= null;
		}
		$this->last	= $sql;
		
		if(MOD_DEBUG)
		{
			$result	= $this->driver->query($sql);
			if(!$result)
			{
				Show::error(sprintf('Fail in exec SQL!<br/>SQL:<i><u>%s</u></i>,<br/>Error:<i><u>%s</u></i> ', $sql, $this->driver->lastError()));
			}
			return $result;
		}
		else
		{
			return $this->driver->query($sql);
		}
	}
	
	
	//----------------------util--------------------------

	function fields()
	{
		static $fields	= array();
		if(!isset($fields[$this->table]))
		{
			try {
				$fields[$this->table]	= import(DATA_PATH.'/cache/fields_'.$this->table.'.php');
			}
			catch(Exception $e)
			{
				$fields[$this->table]	= $this->driver->fields($this->table);
				if(!MOD_DEBUG)
				{
					file_put_contents(
						DATA_PATH."/cache/fields_{$this->table}.php",
						'<'."?php\nreturn ".var_export($fields[$this->table], true).";\n?".'>'
						);
				}
			}
		}
		return $fields[$this->table];
	}
	
	function validMap($data)
	{
		$tmp	= array();
		$map	= $this->fields();
		foreach($data as $k=>$v)
		{
			if(isset($map[$k]) || (($k=strtolower($k))&&isset($map[$k])))
			switch($map[$k])
			{
			case 0:	//string
				$tmp[$k]	= $this->driver->qstr($v);
				break;
			case 1:	//iteger
				$tmp[$k]	= intval($v);
				break;
			case 2:	//float
				$tmp[$k]	= floatval($v);
				break;
			}
		}
		return $tmp;
	}
	
	function buildRequest($data, $sepr=',')
	{
		if(!is_array($data))
		{
			return $data;
		}
		
		$qu		= $this->driver->qu();
		$s		= array();
		$data	= $this->validMap($data);
		foreach($data as $k=>$d)
		{
			$s[]	= "{$qu}{$k}{$qu}='{$d}'";
		}
		return join($sepr, $s);
	}
	
	/**
	 * 生成SQL条件部分的方法，支持以下形式的参数:
	 *   1.常规值判断表达式
	 *     array('a'=>'b','c'=>'d') <=> SQL: 'a="b" and c="d"'
	 *   2.SQL语法表达式
	 *     array('a','=','b'), array('c','=','d') <=> SQL: 'a=b and c=d'
	 *     array('a'=>'b'), 'OR', array('c'=>'d') <=> SQL: 'a="b" or c="d"'
	 *     array('e','is', 'null')  <=> SQL: 'e is null'
	 *     array('f','=','""')      <=> SQL: 'f=""'
	 *   3.直接写SQL
	 *     a!=b and c=d				<=> SQL: 'a!=b and c=d'
	 */
	function toCondition($ary, $_bewith=false)
	{
		static $lastC;
		
		if($_bewith)
		{
			$lastC = 'AND'; //'AND' by convention
			$tary = array();
			$tstr = '';
			foreach($ary as $a) {
				if($a && ($tstr=$this->toCondition($a))){
					$tary[] = $tary ? "{$lastC} $tstr" : $tstr;
				}
			}
			return join(' ', $tary);
		}
		elseif(!$ary) {
			return '';
		}
		elseif(count($ary)===3 && is_int(key($ary)))
		{
			if(is_array($ary[2])){
				$ary[2] = '('.join(',', array_map(array(__CLASS__,'quote'), $ary[2])).')';
			}
			return join(' ', $ary);
		}
		elseif(is_string($ary)) {
			if(in_array($ary, array('OR','AND'))){
				$lastC = $ary;
				return '';
			}
			else{
				return $ary;
			}
		}
		else
		{
			if(isset($ary['_pk']))
			{
				$ary[self::pk()]	=   $ary['_pk'];
				unset($ary['_pk']);
			}
			return $this->buildRequest($ary, ' AND ');
		}
	}
	
	//-------------------------------helper methods------------------------------
	
	function pk()
	{
		$o  =   $this->fields();
		return isset($o['_pk']) ? $o['_pk'] : null;
	}

	function insertId()
	{
		return $this->driver->insertId();
	}
	function affectedRows()
	{
		return $this->driver->affectedRows();
	}
	function lastError()
	{
		return $this->driver->lastError();
	}
	function lastSql()
	{
		return $this->last;
	}
	function dumpMode($non_exec=true, $dumpfile=null)
	{
		$this->dump		= $non_exec ? 2 : 1;
		$this->dumpFile	= $dumpfile;
		return $this;
	}
	
	static function qstr($str) { return mysql_escape_string($str); }

	function quote($str){ return '"'.addslashes($str).'"'; }
	
	function increase($field, $cond, $step=1)
	{
		$this->update("{$field}={$field}+{$step}")->where($cond)->exec();
		return $this->affectedRows();
	}
	function decrease($field, $cond, $step=1)
	{
		$this->update("{$field}={$field}-{$step}")->where($cond)->exec();
		return $this->affectedRows();
	}
	
	/**
	 * 返回事务对象,接口见DbTransaction.php
	 * @return MySqliTransaction
	 */
	function trans(){
		return $this->driver->trans;
	}

}

?>
